use std::str::FromStr;

use clap::{
    builder::{NonEmptyStringValueParser, PossibleValuesParser, TypedValueParser},
    Parser, Subcommand,
};
use log::LevelFilter;

use crate::table::visualize::Style;

/// The CS2 Server Cache Controller.
#[derive(Debug, Clone, Parser)]
#[clap(name = "cs2s-ctl", version = cs2s::build_info::version(), author)]
pub struct CliArguments {
    #[clap(subcommand)]
    pub command: Command,

    /// Name of the server.
    ///
    /// This name is used to identify the server in the whole system, which should be
    /// unique.
    #[clap(
        short,
        long,
        value_parser = NonEmptyStringValueParser::new(),
        env = "CS2S_NAME",
    )]
    pub name: String,
}

#[derive(Debug, Clone, Subcommand)]
pub enum Command {
    /// Visualize the cache tables.
    #[clap(subcommand)]
    Table(TableCommand),

    /// Manage the server.
    #[clap(subcommand)]
    Server(ServerCommand),
}

#[derive(Debug, Clone, Subcommand)]
pub enum TableCommand {
    /// List all cache tables.
    #[clap(name = "ls")]
    List(TableListCommand),

    /// Show the content of a cache table.
    Show(TableShowCommand),
}

#[derive(Debug, Clone, Parser)]
pub struct TableListCommand {
    #[clap(flatten)]
    pub shared_args: TableCommandSharedArgs,
}

#[derive(Debug, Clone, Parser)]
pub struct TableShowCommand {
    #[clap(flatten)]
    pub shared_args: TableCommandSharedArgs,

    /// Path used to identify the table.
    ///
    /// Cache tables are created to store the cache data for programs, libraries, etc.
    /// To identify a specific table, path to the program, library, etc. is used as the
    /// identifier.
    pub table: String,

    /// Index of slot to show.
    ///
    /// If not specified, show all non-empty slots. Otherwise, show the detail of the
    /// specified slot.
    #[clap(short, long, value_name = "INDEX")]
    pub slot: Option<usize>,
}

#[derive(Debug, Clone, Parser)]
pub struct TableCommandSharedArgs {
    /// Style of the output.
    #[clap(long, default_value_t = Style::Human, env = "CS2S_CTL_STYLE")]
    pub style: Style,
}

#[derive(Debug, Clone, Subcommand)]
pub enum ServerCommand {
    /// Start the server.
    Start(ServerStartCommand),

    /// Stop the server.
    Stop(ServerStopCommand),
}

#[derive(Debug, Clone, Parser)]
pub struct ServerStartCommand {
    /// Congestion threshold of the server.
    ///
    /// The congestion threshold is used to determine the congestion status of the
    /// server. If the congestion status is higher than the threshold, the server will
    /// report the congestion as a warning.
    ///
    /// To mitigate the congestion, the system administrator should increase the maximum
    /// and default capacity of the POSIX message queue. On Linux, they can be set
    /// through `/proc/sys/fs/mqueue/msg_max` and `/proc/sys/fs/mqueue/msg_default`.
    #[clap(short = 'T', long, default_value_t = 0.8)]
    pub congestion_threshold: f64,

    /// Level of the log.
    ///
    /// The log level is used to control the verbosity of the log. The higher the level,
    /// the more verbose the log is.
    ///
    /// Note that the log level is case-insensitive.
    #[clap(
        long,
        default_value_t = LevelFilter::Info,
        env = "CS2S_CTL_LOG_LEVEL",
        value_parser = PossibleValuesParser::new(LevelFilter::iter().map(|level| level.as_str()))
            .try_map(|s| LevelFilter::from_str(&s)),
        ignore_case = true,
    )]
    pub log_level: LevelFilter,
}

#[derive(Debug, Clone, Parser)]
pub struct ServerStopCommand {
    /// Whether stop the server with failure.
    #[clap(short = 'F', long, default_value_t = false)]
    pub fail: bool,
}
